Обновить

Implementation of the new NTCP2 transport protocol for the I2P network

Время на прочтение 6 min
Количество просмотров 8.8K
I2P transport protocols were developed almost 15 years ago, when the main goal was to hide the content of traffic, and not the fact that a particular protocol was used. Nobody took DPI (deep packets inspection) and traffic blocking into account at that time. However, times are changing and although existing I2P protocols are still quite secure, there is a need for a new transport protocol that responds to existing and future threats, and, first of all, DPI, which analyzes the length of packets. In addition, the new protocol uses the latest advances in cryptography. Full description of the protocol Here. Based on Noise, in which SHA256 is used as the hash function, and x as the DH (in Noise terminology)25519.

image

New cryptography


For NTCP2, in addition to those already existing in I2P, it is necessary to implement the following cryptographic algorithms:

  • x25519
  • HMAC-SHA256
  • Chacha20
  • Poly1305
  • AEAD
  • Siphash

All of them, with the exception of Siphash, are implemented in openssl 1.1.0. In turn, Siphash will appear in openssl 1.1.1, which will be released in the near future. For compatibility with openssl 1.0.2, which is included in most operating systems currently in use, i2pd was added with its own implementations written by one of the i2pd developers Jeff Becker-om, known in I2P as psi.

Compared to NTCP, x25519 replaces DH, AEAD/Chaha20/Poly1305 replaces AES-256-CBC/Adler32, and Siphash is used to encrypt the length of transmitted messages. The public key calculation procedure has become more complex, with many HMAC-SHA calls256.

Changes to RouterInfo


To work using the NTCP2 protocol, in addition to the two existing keys (encryption and signature), a third x25519 key is introduced, called a static key, which must be present in some RouterInfo address as the “s” parameter for both clients and servers. If more than one address supports NTCP2, for example ipv4 and ipv6, then “s” must be the same everywhere. For clients, the address can only contain “s” and not contain the “host” and “port” parameters. Also a required NTCP2 parameter is “v”, currently always equal to «2».

The NTCP2 address can be specified as an “NTCP” type address with additional parameters - in this case, the connection can be established via both NTCP and NTCP2, or as an “NTCP2” type address that supports only NTCP2 connections. Java I2P uses the first method, i2pd uses the second.

If a node accepts incoming NTCP2 connections, then it must publish the parameter "i" with the value IV to encrypt the public key when establishing a connection.

Establishing a connection


During the connection establishment process, the parties generate x25519 temporary key pairs, and based on them and the static keys, they calculate key sets for data transmission. Static keys are also verified against the contents of RouterInfo..

The parties exchange three messages:

SessionRequest ------------------->
< — SessionCreated
SessionConfirmed ----------------->

for each of which a common x25519 key called “input key material” is calculated and then a message encryption key is generated using the MixKey operation, with the value ck (chaining key) stored between messages and being the result on the basis of which keys for data transmission are calculated . The MixKey implementation looks something like this:

MixKey code
 void NTCP2Establisher::MixKey (const uint8_t * inputKeyMaterial, uint8_t * derived)
{
	// temp_key = HMAC-SHA256(ck, input_key_material)
	uint8_t tempKey[32]; unsigned int len;
	HMAC(EVP_sha256(), m_CK, 32, inputKeyMaterial, 32, tempKey, &len); 	
	// ck = HMAC-SHA256(temp_key, byte(0x01)) 
	static uint8_t one[1] =  { 1 };
	HMAC(EVP_sha256(), tempKey, 32, one, 1, m_CK, &len); 	
	// derived = HMAC-SHA256(temp_key, ck || byte(0x02))
	m_CK[32] = 2;
	HMAC(EVP_sha256(), tempKey, 32, m_CK, 33, derived, &len); 	
}


SessionRequest consists of a 32-byte public key x25519 of the client, and an AEAD/Chacha20/Poly1305 encrypted 16-byte data block + 16 bytes of hash, as well as a set of random data (padding), the length of which is transmitted in the encrypted block. The length of the second half of the SessionConfirmed message is also transmitted there. The block is encrypted and signed with a key based on the client's temporary key and the server's static key. The initial ck for the MixKey is set to SHA256 (“Noise_XKaesobfse+hs2+hs3_25519_ChaChaPoly_SHA256»).

Since the 32 bytes of the x25519 public key can be recognized by dpi, they are encrypted using AES-256-CBC, where the key is a hash of the server address, and the IV is taken from the “i” parameter of the address in RouterInfo.

SessionCreated similar in structure to SessionRequest, except that the key is calculated based on the temporary keys of both parties, and the IV after decryption/encryption of the public key from SessionRequest is taken as the IV for encrypting/decrypting the public key.

SessionConfirmed consists of two parts: the client's static public key and the client's RouterInfo. Unlike previous posts, the public key is encrypted by AEAD/Chaha20/Poly1305 with the same key as SessionCreated. Therefore, the length of the first part is not 32, but 48 bytes. The second part is also encrypted with AEAD/Chaha20/Poly1305, but with a new key, calculated based on the server’s temporary key and the client’s static key. A block of random data can also be added to the RouterInfo, but this is generally not necessary because the length of the RouterInfo varies.

Generating keys for data transfer


If all checks of hashes and keys during the connection setup process were successful, then after the last MixKey there should be the same ck on both sides, from which 2 sets of key triples will be generated in each direction, where k is the AEAD/Chaha20/Poly1305 key, sipk is the key for Siphash, sipiv is the initial IV value for Siphash, which changes after each use.

Code that implements key generation
void NTCP2Session::KeyDerivationFunctionDataPhase ()
{
	uint8_t tempKey[32]; unsigned int len;
        // temp_key = HMAC-SHA256(ck, zerolen)
	HMAC(EVP_sha256(), m_Establisher->GetCK (), 32, nullptr, 0, tempKey, &len); 
	static uint8_t one[1] =  { 1 };
        // k_ab = HMAC-SHA256(temp_key, byte(0x01)).
	HMAC(EVP_sha256(), tempKey, 32, one, 1, m_Kab, &len); 
	m_Kab[32] = 2;
        // k_ba = HMAC-SHA256(temp_key, k_ab || byte(0x02))
	HMAC(EVP_sha256(), tempKey, 32, m_Kab, 33, m_Kba, &len);  
	static uint8_t ask[4] = { 'a', 's', 'k', 1 }, master[32];
        // ask_master = HMAC-SHA256(temp_key, "ask" || byte(0x01))
	HMAC(EVP_sha256(), tempKey, 32, ask, 4, master, &len); 
	uint8_t h[39];
	memcpy (h, m_Establisher->GetH (), 32);
	memcpy (h + 32, "siphash", 7);
        // temp_key = HMAC-SHA256(ask_master, h || "siphash")
	HMAC(EVP_sha256(), master, 32, h, 39, tempKey, &len); 
        // sip_master = HMAC-SHA256(temp_key, byte(0x01))  
	HMAC(EVP_sha256(), tempKey, 32, one, 1, master, &len); 
        // temp_key = HMAC-SHA256(sip_master, zerolen)
	HMAC(EVP_sha256(), master, 32, nullptr, 0, tempKey, &len); 
       // sipkeys_ab = HMAC-SHA256(temp_key, byte(0x01)).
	HMAC(EVP_sha256(), tempKey, 32, one, 1, m_Sipkeysab, &len); 
	m_Sipkeysab[32] = 2;
         // sipkeys_ba = HMAC-SHA256(temp_key, sipkeys_ab || byte(0x02)) 
	HMAC(EVP_sha256(), tempKey, 32, m_Sipkeysab, 33, m_Sipkeysba, &len);
}


The first 16 bytes of the sipkeys array represent the Siphash key, the second 8 bytes represent the IV.
Siphash actually requires two 8 byte keys, but i2pd treats them as 1 16 byte key.

Data transfer


Data is transmitted in frames, each frame consists of 3 parts:

  1. 2 byte frame length encrypted by Siphash
  2. data encrypted by Chacha20
  3. 16 Poly hash byte1305

The maximum length of transmitted data in one frame is 65519 bytes.

The message length is encrypted by XORing the first two bytes of the current IV Siphash.

The data consists of blocks, each block is preceded by a 3-byte header with the block type and length. Mostly I2NP type blocks are transmitted, containing I2NP messages with a modified header. Several I2NP blocks can be transmitted in one frame.

Another important block type is the random data block, which is recommended to be added to each frame. He can only be one and the last.

In addition to them, in the current implementation of NTCP2 there are 3 more block types:

  • RouterInfo - usually contains the RouterInfo of the server immediately after the connection is established, but the RouterInfo of an arbitrary node can be transmitted at any time in order to speed up the operation of floodfills, for which a flag field is provided in the message.
  • Termination - sent by the node when the connection is terminated on its initiative, indicating the reason.
  • DateTime - current time in seconds.

Thus, the new transport protocol allows not only to effectively resist DPI, but also significantly reduces the load on the processor due to more modern and faster cryptography, which is especially important when working on weak devices such as smartphones and routers. Currently, NTCP2 support is fully implemented in both the official I2P and i2pd and will appear officially in the next releases 0.9.36 and 2.20, respectively. To enable ntcp2 in i2pd, you must specify the configuration parameter ntcp2.enabled=true, and ntcp2.published=true and ntcp2.port=&lt;port&gt; for incoming connections.
Tags:
Hubs:
Всего голосов 32: ↑32 и ↓0 +32
Комментарии 9
+9

Comments 9

I like the concept of i2p as a "parallel internet". But practice shows that few people need it. TOR is much more popular precisely because you can access the regular network through it.
Well, of course, the implementation is still lame. A year ago I was forced to remove the i2p node from the router, because no amount of tricks could get it to consume less CPU (it costs one of the first atoms).

Java or i2pd? For i2pd the parameter precomputation.elgamal=true includes: This, which dramatically reduces the load on the processor.
NTCP2 does not use this braking operation modpow at all.
There was java. Thanks, I'll read about i2pd.
I like the concept of i2p as a "parallel internet". But practice shows that few people need it. TOR is much more popular precisely because you can access the regular network through it.
Absolutely agree. Let's hope that i2p will become more convenient and stable over time. I like this concept too. Make I2P great again! :)
I see Tor as a transitional technology. It just has significant funding and more developers, including some paid ones. It is already included in many router firmwares and has its own browser. I2P just needs time.
i2p will become even slower?
With NTCP2 i2p has become faster, while the pre-release version is available. We are waiting for the release ;)
the OpenWRT repository does not have the latest version 2.22, ~500mhz routers no longer support i2pd?
p.s. It would be nice if there were clear instructions for deployment on xxWRT. Or the default config with the necessary changes indicated.
We ourselves do not build for OpenWRT. This is what he does LLE.
Here's the topic github.com/PurpleI2P/i2pd/issues/345
It seems like everything is fine there since 2.22.
Only full-fledged users can leave comments. Sign in, Please.